package handler.webService.jbossws;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPMessageContext;

import org.apache.log4j.Logger;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import static handler.webService.jbossws.constants.StaticFields.*;



public class FilterHandler implements javax.xml.ws.handler.soap.SOAPHandler<SOAPMessageContext> {
	private static InetAddress IPAddress;
	private Logger l=Logger.getLogger(this.getClass());
	private HashMap<String, String> node_name = new HashMap<String, String>();
	private HashMap<String, byte[]> byte_session = new HashMap<String, byte[]>();
	private HashMap<String, byte[]> byteInbound= new HashMap<String, byte[]>();
	private static final String FILTERcontrol="FILTERcontrol";
	private static int UDP_PACK_SIZE;
	private static String IP_ADDRESS;
	private static int UDP_PORT;
	

	public FilterHandler() throws Exception{
		Thread t = new Thread();
		Properties p = new Properties();
		p.load(t.getContextClassLoader().getResourceAsStream("handler.properties"));
		IP_ADDRESS=(String) p.get("eventQueue.input.inetAddress");
		UDP_PORT=Integer.parseInt((String)p.get("eventQueue.input.udpPort"));
		UDP_PACK_SIZE=Integer.parseInt((String)p.get("eventQueue.input.datagramSize"));
		l.info("eventQueue.input.inetAddress = "+IP_ADDRESS);
		l.info("eventQueue.input.udpPort = "+UDP_PORT);
		l.info("eventQueue.input.inetAddress = "+UDP_PACK_SIZE);

		IPAddress = InetAddress.getByName(IP_ADDRESS);
		node_name.put(NODE_NAME[0], "");
		node_name.put(NODE_NAME[1], ""); 
		node_name.put(NODE_NAME[2], "");
	}

	public Set<QName> getHeaders() {
		return null;
	}

	public void close(MessageContext arg0){
		Class<? extends MessageContext> cl = arg0.getClass();
		if(cl.getSimpleName().equals("SOAPMessageContextJAXWS")){
		/*	SOAPMessageContextJAXWS msg = (SOAPMessageContextJAXWS) arg0;		
			try {
				SOAPBody body = msg.getSOAPMessage().getSOAPBody();
				if(body.hasFault()){
					SOAPFault d = body.getFault();
					String codiceFault=d.getFaultCodeAsName().getLocalName();
					if(codiceFault.equals("Server")){
						l.info(codiceFault);
						//d.removeAllFaultSubcodes();
					}
				}
			} catch (SOAPException e) {
				e.getMessage();
			}*/
		}
	}

	public boolean handleFault(SOAPMessageContext arg0) {
		DatagramSocket clientSocket=null;
		try {
			SOAPFault fault = arg0.getMessage().getSOAPBody().getFault();
			l.error("Failed to execute a SOAP call"); 
			HttpServletResponse resp=(HttpServletResponse)arg0.get(MessageContext.SERVLET_RESPONSE);
			String encode=resp.getHeader(FILTERcontrol);
			if(encode==null	){
				l.error("handleFault: Il messaggio non contiene le informazioni richieste");
				return false;
			}

			if(!byteInbound.containsKey(encode)){
				l.error("handleFault: Le informazioni richieste non sono coerenti nell'inbound");
				return false;
			}
			byte[] sessionB=byte_session.get(encode);
			byte[] faultDescB=fault.getFaultString().getBytes();
			byte[] keyInbound=byteInbound.get(encode);
			byte_session.remove(encode);
			byteInbound.remove(encode);
			int size = KEY_SIZE + FAIL_DESC_B +faultDescB.length+ keyInbound.length;
			
			ByteBuffer failBB = ByteBuffer.allocate(size);
			failBB.put(FAILB);
			failBB.putInt(size);
			failBB.putLong(System.currentTimeMillis());
			failBB.put(sessionB);
			failBB.putInt(faultDescB.length);
			failBB.put(faultDescB);
			failBB.put(keyInbound);
			byte[] sendData = new byte[UDP_PACK_SIZE];
			sendData = failBB.array();

			DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, UDP_PORT);
			//l.info("send packet (capacity : "+failBB.capacity()+")...");
			clientSocket = new DatagramSocket();
			clientSocket.send(sendPacket);
		} catch (IOException e) {
			if(clientSocket!=null)
				if(!clientSocket.isClosed())
					clientSocket.close();
			l.error("handleFault: IOException "+e.getMessage());
			return false;
		} catch (SOAPException e) {
			l.error("handleFault: IOException "+e.getMessage());
			return false;
		}
		return true;
	}

	public boolean handleMessage(SOAPMessageContext msg) {
		Boolean outbound = (Boolean) msg.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
		if (outbound)
			return handleOutbound(msg);
		else
			return handleInbound(msg);
	}

	@SuppressWarnings("static-access")
	private boolean handleInbound(SOAPMessageContext msg){
		DatagramSocket clientSocket=null;
		try {
			HttpServletRequest req = (HttpServletRequest)msg.get(MessageContext.SERVLET_REQUEST); 
			SOAPMessage soapMessage =  msg.getMessage(); 
			SOAPHeader soapHeader = soapMessage.getSOAPHeader();
			SOAPBody soapBody = soapMessage.getSOAPBody();
			if (soapBody == null){
				l.error("handleInbound: Il messaggio non contiene un Body");
				return false;
			}
			if((soapHeader == null) || !findInfoHeader(soapHeader)){
				l.error("handleInbound: Il messaggio non contiene gli elementi Header richiesti");
				return false;
			}
			//l.info(node_name);
			String sequence = node_name.get(NODE_NAME[0]);
			String username=node_name.get(NODE_NAME[1]);
			String password = node_name.get(NODE_NAME[2]);
			String localName=req.getLocalName();
			if(localName.equalsIgnoreCase("127.0.0.1"))
				localName="localhost";
			String applURL="http://"+localName+":"+req.getLocalPort()+req.getContextPath();
			QName op = (QName)msg.get(msg.WSDL_OPERATION);
			String NSBody= op.getNamespaceURI();
			String operation=op.getLocalPart();
			byte[] parB=findParameter(soapBody,op.getLocalPart());
			long timestamp = System.currentTimeMillis();
			String session=req.getSession().getId();
			l.info("sessioneINB: "+session);
			
			byte[] sessionB=session.getBytes();
			byte[] sequenceB=sequence.getBytes();
			byte[] usernameB=username.getBytes();
			byte[] passwordB=password.getBytes();
			byte[] applURLB=applURL.getBytes();
			byte[] NSBodyB=NSBody.getBytes();
			byte[] operationB=operation.getBytes();
			
			int size = 	KEY_SIZE 					+ 
						CONTENT_MIN_SIZE 			+
						sequenceB.length			+
						usernameB.length 			+
						passwordB.length 			+ 
						applURLB.length				+
						NSBodyB.length				+
						operationB.length 		 
						;
			if(parB!=null)
				size=size+parB.length;

			String control=INB+Integer.toString(size)+Long.toString(timestamp)+session;
			HttpServletResponse a=(HttpServletResponse)msg.get(MessageContext.SERVLET_RESPONSE);
			a.addHeader(FILTERcontrol, control);
			
			ByteBuffer bb = ByteBuffer.allocate(size);
			bb.put(INB);
			bb.putInt(size);
			bb.putLong(timestamp);
			bb.put(sessionB);
			bb.putInt(sequenceB.length);
			bb.put(sequenceB);
			bb.putInt(usernameB.length);
			bb.put(usernameB);
			bb.putInt(passwordB.length);
			bb.put(passwordB);
			bb.putInt(applURLB.length);
			bb.put(applURLB);
			bb.putInt(NSBodyB.length);
			bb.put(NSBodyB);
			bb.putInt(operationB.length);
			bb.put(operationB);
			if(parB!=null)
				bb.put(parB);
			byte[] sendData = new byte[UDP_PACK_SIZE];
			sendData = bb.array();
			
			DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, UDP_PORT);
			//l.info("send packet (capacity : "+bb.capacity()+")...");
			byteInbound.put(control, sendData);
			byte_session.put(control, sessionB);
			clientSocket = new DatagramSocket();
			clientSocket.send(sendPacket);
		} catch (SOAPException e) {
			l.error("SOAPException "+e.getMessage());
			return false;
		}catch(UnsupportedOperationException e){ 
			l.error("handleInbound: UnsupportedOperationException "+e.getMessage());
			return false;
		}catch(ClassCastException e){ 
			l.error("handleInbound: ClassCastException "+e.getMessage());
			return false;
		}catch(NullPointerException e){ 
			l.error("handleInbound: NullPointerException "+e.getMessage());
			return false;
		}catch(IllegalArgumentException e){ 
			l.error("handleInbound: IllegalArgumentException "+e.getMessage());
			return false;
		}catch (IOException e) {
			if(clientSocket!=null)
				if(!clientSocket.isClosed())
					clientSocket.close();
			l.error("handleInbound: IOException "+e.getMessage());
			return false;
		} 
		
		return true;
	}

	private boolean handleOutbound(SOAPMessageContext msg) {
		DatagramSocket clientSocket=null;
		HttpServletResponse resp=(HttpServletResponse)msg.get(MessageContext.SERVLET_RESPONSE);
		String control=resp.getHeader(FILTERcontrol);
		if(control==null	){
			l.error("handleOutbound: Il messaggio non contiene le informazioni richieste");
			return false;
		}
		try {
			if(!byteInbound.containsKey(control)){
				l.error("handleOutbound: Le informazioni richieste non sono coerenti nell'inbound");
				return false;
			}

			byte[] sessionB=byte_session.get(control);
			byte[] keyInbound=byteInbound.get(control);
			byte_session.remove(control);
			byteInbound.remove(control);
			int size = KEY_SIZE + keyInbound.length;
			l.info("sessioneOUT: "+new String(sessionB));
			ByteBuffer outboundBB = ByteBuffer.allocate(size);
			outboundBB.put(OUTB);
			outboundBB.putInt(size);
			outboundBB.putLong(System.currentTimeMillis());
			outboundBB.put(sessionB);
			outboundBB.put(keyInbound);
			byte[] sendData = new byte[UDP_PACK_SIZE];
			sendData = outboundBB.array();
			
			DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, UDP_PORT);
			//l.info("send packet (capacity : "+bb.capacity()+")...");
			clientSocket = new DatagramSocket();
			clientSocket.send(sendPacket);
		} catch (IOException e) {
			if(clientSocket!=null)
				if(!clientSocket.isClosed())
					clientSocket.close();
			l.error("handleOutbound: IOException "+e.getMessage());
			return false;
		}
		return true;
	}
		
	@SuppressWarnings("unchecked")
	private boolean findInfoHeader(SOAPHeader soapHeader) {
		int numNodes=node_name.size();
		if(soapHeader.getElementQName().getLocalPart().equalsIgnoreCase(new String("Header"))){
			Iterator<SOAPElement> parent = soapHeader.getChildElements();
			int count=0;
			while(parent.hasNext()){
				Object p = parent.next();
				if(p instanceof SOAPElement){
					SOAPElement soapParent = (SOAPElement)p;
					if(soapParent.getLocalName().equalsIgnoreCase(PAR_NODE_NAME)){
						Iterator<SOAPElement>ce = soapParent.getChildElements();
						count=0;
						while(ce.hasNext()){
							Object o= ce.next();
							if(o instanceof SOAPElement){
								SOAPElement se = (SOAPElement) o;
								if(se!=null && node_name.containsKey(se.getLocalName()) && se.getFirstChild()!=null){
									node_name.put(se.getLocalName(), se.getFirstChild().getNodeValue());
									count++;
								}
							}
						}

					}
					else{
						SOAPElement se = (SOAPElement)p;
						if(se!=null && node_name.containsKey(se.getLocalName()) && se.getFirstChild()!=null){
							node_name.put(se.getLocalName(), se.getFirstChild().getNodeValue());
							count++;
						}
					}
					if(count == numNodes)
						return true;
				}
			}
		}
		return false;
	}

	private byte[] findParameter(SOAPBody body, String operationName){
		NodeList nc = body.getChildNodes();
		boolean trovato=false;
		Node operation=null;
		int j=0;
		while(j<nc.getLength() && !trovato){
			operation = nc.item(j);
			if(operation.getLocalName()!=null && operation.getLocalName().equalsIgnoreCase(operationName))
				trovato=true;
			j++;
		}	
		if(trovato==true){
			NodeList nlOperation = operation.getChildNodes();
			int numChild=nlOperation.getLength();
			if (numChild>0){
				ArrayList<byte[]> tmpByte = new ArrayList<byte[]>(numChild*2);
				int size=NUM_PAR_B;
				int numRealChild=0;
				for(int i=0;i<numChild;i++){
					Node node = nlOperation.item(i);
					if(node.hasChildNodes() && node.getLocalName()!=null){
						byte[] valueB = node.getFirstChild().getNodeValue().getBytes();
						size+=PAR_B+(valueB.length);
						tmpByte.add(valueB);
						numRealChild++;
					}else 
						tmpByte.add((byte[])null);
				}
				ByteBuffer bb = ByteBuffer.allocate(size);
				bb.putInt(numRealChild);
				for(int i=0;i<numChild;i++){
					byte[] valueB = tmpByte.get(i);
					if(valueB!=null){
						bb.putInt(valueB.length);
						bb.put(valueB);
					}
				}
				return bb.array();
			}
		}
		return (byte[])null;
	}
}